#include "wx/platform.h"
#include "wx/gtk/win_gtk.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct _GtkPizzaChild GtkPizzaChild;
typedef struct _GtkPizzaClass GtkPizzaClass;

struct _GtkPizzaClass {
  GtkContainerClass parent_class;
  void ( *set_scroll_adjustments )( GtkPizza *pizza, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment );
};

struct _GtkPizzaChild {
  GtkWidget *widget;
  gint x;
  gint y;
};

static void gtk_pizza_class_init( GtkPizzaClass *klass );
static void gtk_pizza_init( GtkPizza *pizza );
static void gtk_pizza_realize( GtkWidget *widget );
static void gtk_pizza_unrealize( GtkWidget *widget );
static void gtk_pizza_map( GtkWidget *widget );
static void gtk_pizza_size_request( GtkWidget *widget, GtkRequisition *requisition );
static void gtk_pizza_size_allocate( GtkWidget *widget, GtkAllocation *allocation );
static void gtk_pizza_style_set( GtkWidget *widget, GtkStyle *previous_style );
static void gtk_pizza_add( GtkContainer *container, GtkWidget *widget );
static void gtk_pizza_remove( GtkContainer *container, GtkWidget *widget );
static void gtk_pizza_forall( GtkContainer *container, gboolean include_internals,
                              GtkCallback callback, gpointer callback_data );

static void gtk_pizza_allocate_child( GtkPizza *pizza, GtkPizzaChild *child );
static GtkType gtk_pizza_child_type( GtkContainer *container );
static void  gtk_pizza_scroll_set_adjustments( GtkPizza *pizza, GtkAdjustment *hadj, GtkAdjustment *vadj );
static GtkWidgetClass* pizza_parent_class;

GtkType gtk_pizza_get_type() {
  static GtkType pizza_type = 0;
  if( !pizza_type ) {
    static const GTypeInfo pizza_info = {
      sizeof( GtkPizzaClass ), NULL, NULL,
      ( GClassInitFunc ) gtk_pizza_class_init, NULL, NULL,
      sizeof( GtkPizza ), 16, ( GInstanceInitFunc ) gtk_pizza_init, NULL
    };
    pizza_type = g_type_register_static( GTK_TYPE_CONTAINER, "GtkPizza", &pizza_info, ( GTypeFlags )0 );
  }
  return pizza_type;
}

#define g_marshal_value_peek_object(v)   g_value_get_object (v)
static void g_cclosure_user_marshal_VOID__OBJECT_OBJECT( GClosure *closure, GValue *return_value, guint n_param_values,
    const GValue *param_values, gpointer invocation_hint, gpointer marshal_data ) {
  typedef void ( *GMarshalFunc_VOID__OBJECT_OBJECT )( gpointer data1, gpointer arg_1, gpointer arg_2, gpointer data2 );
  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
  register GCClosure *cc = ( GCClosure* ) closure;
  register gpointer data1, data2;
  g_return_if_fail( n_param_values == 3 );
  if( G_CCLOSURE_SWAP_DATA( closure ) ) {
    data1 = closure->data;
    data2 = g_value_peek_pointer( param_values + 0 );
  } else {
    data1 = g_value_peek_pointer( param_values + 0 );
    data2 = closure->data;
  }
  callback = ( GMarshalFunc_VOID__OBJECT_OBJECT )( marshal_data ? marshal_data : cc->callback );
  callback( data1, g_marshal_value_peek_object( param_values + 1 ), g_marshal_value_peek_object( param_values + 2 ),
            data2 );
}

static void gtk_pizza_class_init( GtkPizzaClass *klass ) {
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkContainerClass *container_class;
  object_class = ( GtkObjectClass* ) klass;
  widget_class = ( GtkWidgetClass* ) klass;
  container_class = ( GtkContainerClass* ) klass;
  pizza_parent_class = gtk_type_class( GTK_TYPE_CONTAINER );
  widget_class->map = gtk_pizza_map;
  widget_class->realize = gtk_pizza_realize;
  widget_class->unrealize = gtk_pizza_unrealize;
  widget_class->size_request = gtk_pizza_size_request;
  widget_class->size_allocate = gtk_pizza_size_allocate;
  widget_class->style_set = gtk_pizza_style_set;
  container_class->add = gtk_pizza_add;
  container_class->remove = gtk_pizza_remove;
  container_class->forall = gtk_pizza_forall;
  container_class->child_type = gtk_pizza_child_type;
  klass->set_scroll_adjustments = gtk_pizza_scroll_set_adjustments;
  widget_class->set_scroll_adjustments_signal = g_signal_new( "set_scroll_adjustments", G_TYPE_FROM_CLASS( object_class ),
      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( GtkPizzaClass, set_scroll_adjustments ), NULL, NULL,
      g_cclosure_user_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT );
}

static GtkType gtk_pizza_child_type( GtkContainer *container ) {
  return GTK_TYPE_WIDGET;
}

static void gtk_pizza_init( GtkPizza *pizza ) {
  GTK_WIDGET_UNSET_FLAGS( pizza, GTK_NO_WINDOW );
  pizza->children = NULL;
  pizza->bin_window = NULL;
  pizza->m_xoffset = 0;
  pizza->m_yoffset = 0;
}

GtkWidget* gtk_pizza_new() {
  GtkPizza *pizza;
  pizza = g_object_new( gtk_pizza_get_type(), NULL );
  return GTK_WIDGET( pizza );
}

gint gtk_pizza_get_xoffset( GtkPizza *pizza ) {
  g_return_val_if_fail( ( pizza != NULL ), -1 );
  g_return_val_if_fail( ( GTK_IS_PIZZA( pizza ) ), -1 );
  return pizza->m_xoffset;
}

gint gtk_pizza_get_yoffset( GtkPizza *pizza ) {
  g_return_val_if_fail( ( pizza != NULL ), -1 );
  g_return_val_if_fail( ( GTK_IS_PIZZA( pizza ) ), -1 );
  return pizza->m_yoffset;
}

void gtk_pizza_set_xoffset( GtkPizza *pizza, gint xoffset ) {
  g_return_if_fail( pizza != NULL );
  g_return_if_fail( GTK_IS_PIZZA( pizza ) );
  pizza->m_xoffset = xoffset;
}

void gtk_pizza_set_yoffset( GtkPizza *pizza, gint yoffset ) {
  g_return_if_fail( pizza != NULL );
  g_return_if_fail( GTK_IS_PIZZA( pizza ) );
  pizza->m_xoffset = yoffset;
}

gint gtk_pizza_get_rtl_offset( GtkPizza *pizza ) {
  gint border;
  g_return_val_if_fail( ( pizza != NULL ), 0 );
  g_return_val_if_fail( ( GTK_IS_PIZZA( pizza ) ), 0 );
  if( !pizza->bin_window ) {
    return 0;
  }
  border = pizza->container.border_width;
  return GTK_WIDGET( pizza )->allocation.width - border * 2;
}

static void gtk_pizza_scroll_set_adjustments( GtkPizza *pizza, GtkAdjustment  *hadj, GtkAdjustment  *vadj ) {
}

void gtk_pizza_put( GtkPizza *pizza, GtkWidget *widget, gint x, gint y, gint width, gint height ) {
  GtkPizzaChild *child_info;
  g_return_if_fail( pizza != NULL );
  g_return_if_fail( GTK_IS_PIZZA( pizza ) );
  g_return_if_fail( widget != NULL );
  child_info = g_new( GtkPizzaChild, 1 );
  child_info->widget = widget;
  child_info->x = x;
  child_info->y = y;
  pizza->children = g_list_append( pizza->children, child_info );
  if( GTK_WIDGET_REALIZED( pizza ) ) {
    gtk_widget_set_parent_window( widget, pizza->bin_window );
  }
  gtk_widget_set_parent( widget, GTK_WIDGET( pizza ) );
  gtk_widget_set_size_request( widget, width, height );
  if( GTK_WIDGET_REALIZED( pizza ) ) {
    gtk_pizza_allocate_child( pizza, child_info );
  }
}

void gtk_pizza_set_size( GtkPizza *pizza, GtkWidget *widget, gint x, gint y, gint width, gint height ) {
  GtkPizzaChild *child;
  GList *children;
  g_return_if_fail( pizza != NULL );
  g_return_if_fail( GTK_IS_PIZZA( pizza ) );
  g_return_if_fail( widget != NULL );
  #ifndef WX_WARN_ILLEGAL_SETSIZE
  if( width < 0 ) {
    width = 0;
  }
  if( height < 0 ) {
    height = 0;
  }
  #endif
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    if( child->widget == widget ) {
      if( child->x != x || child->y != y ) {
        child->x = x;
        child->y = y;
        gtk_widget_queue_resize( widget );
      }
      gtk_widget_set_size_request( widget, width, height );
      return;
    }
  }
}

static void gtk_pizza_map( GtkWidget *widget ) {
  GtkPizza *pizza;
  GtkPizzaChild *child;
  GList *children;
  g_return_if_fail( widget != NULL );
  g_return_if_fail( GTK_IS_PIZZA( widget ) );
  GTK_WIDGET_SET_FLAGS( widget, GTK_MAPPED );
  pizza = GTK_PIZZA( widget );
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    if( GTK_WIDGET_VISIBLE( child->widget ) &&
        !GTK_WIDGET_MAPPED( child->widget ) ) {
      gtk_widget_map( child->widget );
    }
  }
  gdk_window_show( widget->window );
  gdk_window_show( pizza->bin_window );
}

static void gtk_pizza_realize( GtkWidget *widget ) {
  GtkPizza *pizza;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GtkPizzaChild *child;
  GList *children;
  int border;
  g_return_if_fail( widget != NULL );
  g_return_if_fail( GTK_IS_PIZZA( widget ) );
  pizza = GTK_PIZZA( widget );
  GTK_WIDGET_SET_FLAGS( widget, GTK_REALIZED );
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  border = pizza->container.border_width;
  attributes.x += border;
  attributes.y += border;
  attributes.width -= 2 * border;
  attributes.height -= 2 * border;
  if( attributes.width < 2 ) {
    attributes.width = 2;
  }
  if( attributes.height < 2 ) {
    attributes.height = 2;
  }
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual( widget );
  attributes.colormap = gtk_widget_get_colormap( widget );
  attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK;
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  widget->window = gdk_window_new( gtk_widget_get_parent_window( widget ),&attributes, attributes_mask );
  gdk_window_set_user_data( widget->window, widget );
  attributes.x = 0;
  attributes.y = 0;
  attributes.event_mask = gtk_widget_get_events( widget );
  attributes.event_mask |= GDK_EXPOSURE_MASK              |
                           GDK_SCROLL_MASK                |
                           GDK_POINTER_MOTION_MASK        |
                           GDK_POINTER_MOTION_HINT_MASK   |
                           GDK_BUTTON_MOTION_MASK         |
                           GDK_BUTTON1_MOTION_MASK        |
                           GDK_BUTTON2_MOTION_MASK        |
                           GDK_BUTTON3_MOTION_MASK        |
                           GDK_BUTTON_PRESS_MASK          |
                           GDK_BUTTON_RELEASE_MASK        |
                           GDK_KEY_PRESS_MASK             |
                           GDK_KEY_RELEASE_MASK           |
                           GDK_ENTER_NOTIFY_MASK          |
                           GDK_LEAVE_NOTIFY_MASK          |
                           GDK_FOCUS_CHANGE_MASK;
  pizza->bin_window = gdk_window_new( widget->window,&attributes, attributes_mask );
  gdk_window_set_user_data( pizza->bin_window, widget );
  widget->style = gtk_style_attach( widget->style, widget->window );
  gtk_style_set_background( widget->style, widget->window, GTK_STATE_NORMAL );
  gtk_style_set_background( widget->style, pizza->bin_window, GTK_STATE_NORMAL );
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    gtk_widget_set_parent_window( child->widget, pizza->bin_window );
  }
}

static void gtk_pizza_unrealize( GtkWidget *widget ) {
  GtkPizza *pizza;
  g_return_if_fail( widget != NULL );
  g_return_if_fail( GTK_IS_PIZZA( widget ) );
  pizza = GTK_PIZZA( widget );
  gdk_window_set_user_data( pizza->bin_window, NULL );
  gdk_window_destroy( pizza->bin_window );
  pizza->bin_window = NULL;
  if( pizza_parent_class->unrealize ) {
    pizza_parent_class->unrealize( widget );
  }
}

static void gtk_pizza_size_request( GtkWidget *widget, GtkRequisition *requisition ) {
  GtkPizza *pizza;
  GtkPizzaChild *child;
  GList *children;
  GtkRequisition child_requisition;
  g_return_if_fail( widget != NULL );
  g_return_if_fail( GTK_IS_PIZZA( widget ) );
  g_return_if_fail( requisition != NULL );
  pizza = GTK_PIZZA( widget );
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    if( GTK_WIDGET_VISIBLE( child->widget ) ) {
      gtk_widget_size_request( child->widget, &child_requisition );
    }
  }
  requisition->width = 2;
  requisition->height = 2;
}

static void gtk_pizza_size_allocate( GtkWidget *widget, GtkAllocation *allocation ) {
  GtkPizza *pizza;
  gint border;
  gint x, y, w, h;
  GtkPizzaChild *child;
  GList *children;
  gboolean only_resize;
  g_return_if_fail( widget != NULL );
  g_return_if_fail( GTK_IS_PIZZA( widget ) );
  g_return_if_fail( allocation != NULL );
  pizza = GTK_PIZZA( widget );
  only_resize = ( ( widget->allocation.x == allocation->x ) && ( widget->allocation.y == allocation->y ) );
  widget->allocation = *allocation;
  border = pizza->container.border_width;
  x = allocation->x + border;
  y = allocation->y + border;
  w = allocation->width - border * 2;
  h = allocation->height - border * 2;
  if( w < 0 ) {
    w = 0;
  }
  if( h < 0 ) {
    h = 0;
  }
  if( GTK_WIDGET_REALIZED( widget ) ) {
    if( only_resize ) {
      gdk_window_resize( widget->window, w, h );
    } else {
      gdk_window_move_resize( widget->window, x, y, w, h );
    }
    gdk_window_resize( pizza->bin_window, w, h );
  }
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    gtk_pizza_allocate_child( pizza, child );
  }
}

static void gtk_pizza_style_set( GtkWidget *widget, GtkStyle  *previous_style ) {
  if( GTK_WIDGET_REALIZED( widget ) ) {
    gtk_style_set_background( widget->style, widget->window, GTK_STATE_NORMAL );
    gtk_style_set_background( widget->style, GTK_PIZZA( widget )->bin_window, GTK_STATE_NORMAL );
  }
  pizza_parent_class->style_set( widget, previous_style );
}

static void gtk_pizza_add( GtkContainer *container, GtkWidget *widget ) {
  g_return_if_fail( container != NULL );
  g_return_if_fail( GTK_IS_PIZZA( container ) );
  g_return_if_fail( widget != NULL );
  gtk_pizza_put( GTK_PIZZA( container ), widget, 0, 0, 20, 20 );
}

static void
gtk_pizza_remove( GtkContainer *container, GtkWidget *widget ) {
  GtkPizza *pizza;
  GtkPizzaChild *child;
  GList *children;
  g_return_if_fail( container != NULL );
  g_return_if_fail( GTK_IS_PIZZA( container ) );
  g_return_if_fail( widget != NULL );
  pizza = GTK_PIZZA( container );
  children = pizza->children;
  while( children ) {
    child = children->data;
    if( child->widget == widget ) {
      gtk_widget_unparent( widget );
      g_return_if_fail( GTK_IS_WIDGET( widget ) );
      pizza->children = g_list_remove_link( pizza->children, children );
      g_list_free( children );
      g_free( child );
      g_return_if_fail( GTK_IS_WIDGET( widget ) );
      break;
    }
    children = children->next;
  }
}

static void gtk_pizza_forall( GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data ) {
  GtkPizza *pizza;
  GtkPizzaChild *child;
  GList *children;
  g_return_if_fail( container != NULL );
  g_return_if_fail( GTK_IS_PIZZA( container ) );
  g_return_if_fail( callback != ( GtkCallback )NULL );
  pizza = GTK_PIZZA( container );
  children = pizza->children;
  while( children ) {
    child = children->data;
    children = children->next;
    ( * callback )( child->widget, callback_data );
  }
}

static void gtk_pizza_allocate_child( GtkPizza *pizza, GtkPizzaChild *child ) {
  GtkAllocation allocation;
  GtkRequisition requisition;
  allocation.x = child->x - pizza->m_xoffset;
  allocation.y = child->y - pizza->m_yoffset;
  gtk_widget_get_child_requisition( child->widget, &requisition );
  allocation.width = requisition.width;
  allocation.height = requisition.height;
  if( gtk_widget_get_direction( GTK_WIDGET( pizza ) ) == GTK_TEXT_DIR_RTL ) {
    gint offset, border;
    offset = GTK_WIDGET( pizza )->allocation.width;
    border = pizza->container.border_width;
    offset -= border * 2;
    allocation.x = offset - child->x - allocation.width + pizza->m_xoffset;
  }
  gtk_widget_size_allocate( child->widget, &allocation );
}

typedef struct {
  GdkWindow* window;
  int dx, dy;
} AdjustData;

static void scroll_adjust( GtkWidget* widget, void* data ) {
  const AdjustData* p = data;
  widget->allocation.x += p->dx;
  widget->allocation.y += p->dy;
  if( widget->window == p->window ) {
    gtk_widget_queue_resize( widget );
    if( GTK_IS_CONTAINER( widget ) ) {
      gtk_container_forall( GTK_CONTAINER( widget ), scroll_adjust, data );
    }
  }
}

void gtk_pizza_scroll( GtkPizza *pizza, gint dx, gint dy ) {
  pizza->m_xoffset += dx;
  pizza->m_yoffset += dy;
  if( pizza->bin_window ) {
    AdjustData data = { pizza->bin_window, -dx, -dy };
    gdk_window_scroll( pizza->bin_window, -dx, -dy );
    gtk_container_forall( GTK_CONTAINER( pizza ), scroll_adjust, &data );
  }
}

#ifdef __cplusplus
}
#endif /* __cplusplus */
